// 版权所有 Epic Games, Inc. 保留所有权利。

#include "LyraAssetManager.h"
#include "LyraLogChannels.h"
#include "LyraGameplayTags.h"
#include "LyraGameData.h"
#include "AbilitySystemGlobals.h"
#include "Character/LyraPawnData.h"
#include "Misc/App.h"
#include "Stats/StatsMisc.h"
#include "Engine/Engine.h"
#include "AbilitySystem/LyraGameplayCueManager.h"
#include "Misc/ScopedSlowTask.h"
#include "System/LyraAssetManagerStartupJob.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(LyraAssetManager)

const FName FLyraBundles::Equipped("Equipped"); // 定义装备包名称

//////////////////////////////////////////////////////////////////////

static FAutoConsoleCommand CVarDumpLoadedAssets( // 控制台命令：转储已加载资源
	TEXT("Lyra.DumpLoadedAssets"),
	TEXT("显示所有通过资源管理器加载且当前在内存中的资源。"),
	FConsoleCommandDelegate::CreateStatic(ULyraAssetManager::DumpLoadedAssets)
);

//////////////////////////////////////////////////////////////////////

#define STARTUP_JOB_WEIGHTED(JobFunc, JobWeight) StartupJobs.Add(FLyraAssetManagerStartupJob(#JobFunc, [this](const FLyraAssetManagerStartupJob& StartupJob, TSharedPtr<FStreamableHandle>& LoadHandle){JobFunc;}, JobWeight)) // 加权启动任务宏
#define STARTUP_JOB(JobFunc) STARTUP_JOB_WEIGHTED(JobFunc, 1.f) // 启动任务宏

//////////////////////////////////////////////////////////////////////

ULyraAssetManager::ULyraAssetManager()
{
	DefaultPawnData = nullptr; // 初始化默认棋子数据为空
}

ULyraAssetManager& ULyraAssetManager::Get()
{
	check(GEngine); // 检查GEngine是否有效

	if (ULyraAssetManager* Singleton = Cast<ULyraAssetManager>(GEngine->AssetManager)) // 尝试转换为LyraAssetManager
	{
		return *Singleton; // 返回单例
	}

	UE_LOG(LogLyra, Fatal, TEXT("DefaultEngine.ini中的AssetManagerClassName无效。必须设置为LyraAssetManager！")); // 输出致命错误

	// 上面的致命错误阻止了此处的调用。
	return *NewObject<ULyraAssetManager>(); // 创建新对象（实际上不会执行到这里）
}

UObject* ULyraAssetManager::SynchronousLoadAsset(const FSoftObjectPath& AssetPath)
{
	if (AssetPath.IsValid()) // 检查资源路径是否有效
	{
		TUniquePtr<FScopeLogTime> LogTimePtr; // 作用域日志时间指针

		if (ShouldLogAssetLoads()) // 检查是否应记录资源加载
		{
			LogTimePtr = MakeUnique<FScopeLogTime>(*FString::Printf(TEXT("同步加载资源 [%s]"), *AssetPath.ToString()), nullptr, FScopeLogTime::ScopeLog_Seconds); // 创建日志时间对象
		}

		if (UAssetManager::IsInitialized()) // 检查资源管理器是否已初始化
		{
			return UAssetManager::GetStreamableManager().LoadSynchronous(AssetPath, false); // 使用流管理器同步加载
		}

		// 如果资源管理器尚未准备好，使用LoadObject。
		return AssetPath.TryLoad(); // 尝试加载资源
	}

	return nullptr; // 返回空指针
}

bool ULyraAssetManager::ShouldLogAssetLoads()
{
	static bool bLogAssetLoads = FParse::Param(FCommandLine::Get(), TEXT("LogAssetLoads")); // 从命令行参数解析是否记录资源加载
	return bLogAssetLoads; // 返回结果
}

void ULyraAssetManager::AddLoadedAsset(const UObject* Asset)
{
	if (ensureAlways(Asset)) // 确保资源有效
	{
		FScopeLock LoadedAssetsLock(&LoadedAssetsCritical); // 范围锁保护
		LoadedAssets.Add(Asset); // 添加到已加载资源集合
	}
}

void ULyraAssetManager::DumpLoadedAssets()
{
	UE_LOG(LogLyra, Log, TEXT("========== 开始转储已加载资源 ==========")); // 开始转储日志

	for (const UObject* LoadedAsset : Get().LoadedAssets) // 遍历已加载资源
	{
		UE_LOG(LogLyra, Log, TEXT("  %s"), *GetNameSafe(LoadedAsset)); // 记录资源名称
	}

	UE_LOG(LogLyra, Log, TEXT("... 已加载池中有 %d 个资源"), Get().LoadedAssets.Num()); // 记录资源数量
	UE_LOG(LogLyra, Log, TEXT("========== 完成转储已加载资源 ==========")); // 完成转储日志
}

void ULyraAssetManager::StartInitialLoading()
{
	SCOPED_BOOT_TIMING("ULyraAssetManager::StartInitialLoading"); // 启动计时范围

	// 这会完成所有扫描工作，即使加载被延迟，现在也需要这样做
	Super::StartInitialLoading(); // 调用父类启动初始加载

	STARTUP_JOB(InitializeGameplayCueManager()); // 添加游戏提示管理器初始化任务

	{
		// 加载基础游戏数据资源
		STARTUP_JOB_WEIGHTED(GetGameData(), 25.f); // 添加游戏数据获取任务（权重25）
	}

	// 运行所有排队的启动任务
	DoAllStartupJobs(); // 执行所有启动任务
}

void ULyraAssetManager::InitializeGameplayCueManager()
{
	SCOPED_BOOT_TIMING("ULyraAssetManager::InitializeGameplayCueManager"); // 游戏提示管理器初始化计时范围

	ULyraGameplayCueManager* GCM = ULyraGameplayCueManager::Get(); // 获取游戏提示管理器
	check(GCM); // 检查管理器是否有效
	GCM->LoadAlwaysLoadedCues(); // 加载始终加载的提示
}


const ULyraGameData& ULyraAssetManager::GetGameData()
{
	return GetOrLoadTypedGameData<ULyraGameData>(LyraGameDataPath); // 获取或加载游戏数据
}

const ULyraPawnData* ULyraAssetManager::GetDefaultPawnData() const
{
	return GetAsset(DefaultPawnData); // 获取默认棋子数据
}

UPrimaryDataAsset* ULyraAssetManager::LoadGameDataOfClass(TSubclassOf<UPrimaryDataAsset> DataClass, const TSoftObjectPtr<UPrimaryDataAsset>& DataClassPath, FPrimaryAssetType PrimaryAssetType)
{
	UPrimaryDataAsset* Asset = nullptr; // 初始化资源为空

	DECLARE_SCOPE_CYCLE_COUNTER(TEXT("加载GameData对象"), STAT_GameData, STATGROUP_LoadTime); // 声明周期计数器
	if (!DataClassPath.IsNull()) // 检查数据类路径是否为空
	{
#if WITH_EDITOR
		FScopedSlowTask SlowTask(0, FText::Format(NSLOCTEXT("LyraEditor", "BeginLoadingGameDataTask", "正在加载GameData {0}"), FText::FromName(DataClass->GetFName()))); // 创建慢任务
		const bool bShowCancelButton = false; // 不显示取消按钮
		const bool bAllowInPIE = true; // 允许在PIE中
		SlowTask.MakeDialog(bShowCancelButton, bAllowInPIE); // 创建对话框
#endif
		UE_LOG(LogLyra, Log, TEXT("正在加载GameData: %s ..."), *DataClassPath.ToString()); // 记录加载日志
		SCOPE_LOG_TIME_IN_SECONDS(TEXT("    ... GameData加载完成！"), nullptr); // 记录加载时间

		// 在编辑器中，这可能被递归调用，因为它是在PostLoad中按需调用的，因此在这种情况下为主资源强制同步加载，其余异步加载
		if (GIsEditor) // 检查是否在编辑器中
		{
			Asset = DataClassPath.LoadSynchronous(); // 同步加载资源
			LoadPrimaryAssetsWithType(PrimaryAssetType); // 加载主资源类型
		}
		else
		{
			TSharedPtr<FStreamableHandle> Handle = LoadPrimaryAssetsWithType(PrimaryAssetType); // 加载主资源类型
			if (Handle.IsValid()) // 检查句柄是否有效
			{
				Handle->WaitUntilComplete(0.0f, false); // 等待直到完成

				// 这应该总是有效
				Asset = Cast<UPrimaryDataAsset>(Handle->GetLoadedAsset()); // 转换为主数据资源
			}
		}
	}

	if (Asset) // 如果资源加载成功
	{
		GameDataMap.Add(DataClass, Asset); // 添加到游戏数据映射
	}
	else
	{
		// 无法加载任何GameData资源是不可接受的。这将导致难以诊断的软故障。
		UE_LOG(LogLyra, Fatal, TEXT("无法在%s加载GameData资源。类型%s。这是不可恢复的，可能意味着您没有正确的数据来运行%s。"), *DataClassPath.ToString(), *PrimaryAssetType.ToString(), FApp::GetProjectName()); // 输出致命错误
	}

	return Asset; // 返回加载的资源
}


void ULyraAssetManager::DoAllStartupJobs()
{
	SCOPED_BOOT_TIMING("ULyraAssetManager::DoAllStartupJobs"); // 启动任务执行计时范围
	const double AllStartupJobsStartTime = FPlatformTime::Seconds(); // 记录开始时间

	if (IsRunningDedicatedServer()) // 检查是否运行在专用服务器上
	{
		// 不需要定期进度更新，只需运行任务
		for (const FLyraAssetManagerStartupJob& StartupJob : StartupJobs) // 遍历启动任务
		{
			StartupJob.DoJob(); // 执行任务
		}
	}
	else
	{
		if (StartupJobs.Num() > 0) // 检查是否有启动任务
		{
			float TotalJobValue = 0.0f; // 总任务值
			for (const FLyraAssetManagerStartupJob& StartupJob : StartupJobs) // 遍历启动任务计算总值
			{
				TotalJobValue += StartupJob.JobWeight; // 累加任务权重
			}

			float AccumulatedJobValue = 0.0f; // 累计任务值
			for (FLyraAssetManagerStartupJob& StartupJob : StartupJobs) // 遍历启动任务
			{
				const float JobValue = StartupJob.JobWeight; // 获取任务权重
				StartupJob.SubstepProgressDelegate.BindLambda([This = this, AccumulatedJobValue, JobValue, TotalJobValue](float NewProgress) // 绑定子步骤进度委托
					{
						const float SubstepAdjustment = FMath::Clamp(NewProgress, 0.0f, 1.0f) * JobValue; // 计算子步骤调整
						const float OverallPercentWithSubstep = (AccumulatedJobValue + SubstepAdjustment) / TotalJobValue; // 计算总体百分比

						This->UpdateInitialGameContentLoadPercent(OverallPercentWithSubstep); // 更新初始游戏内容加载百分比
					});

				StartupJob.DoJob(); // 执行任务

				StartupJob.SubstepProgressDelegate.Unbind(); // 解绑子步骤进度委托

				AccumulatedJobValue += JobValue; // 累加任务值

				UpdateInitialGameContentLoadPercent(AccumulatedJobValue / TotalJobValue); // 更新初始游戏内容加载百分比
			}
		}
		else
		{
			UpdateInitialGameContentLoadPercent(1.0f); // 如果没有任务，直接更新为100%
		}
	}

	StartupJobs.Empty(); // 清空启动任务数组

	UE_LOG(LogLyra, Display, TEXT("所有启动任务耗时 %.2f 秒完成"), FPlatformTime::Seconds() - AllStartupJobsStartTime); // 记录完成时间
}

void ULyraAssetManager::UpdateInitialGameContentLoadPercent(float GameContentPercent)
{
	// 可以将其路由到早期启动加载屏幕
}

#if WITH_EDITOR
void ULyraAssetManager::PreBeginPIE(bool bStartSimulate)
{
	Super::PreBeginPIE(bStartSimulate); // 调用父类方法

	{
		FScopedSlowTask SlowTask(0, NSLOCTEXT("LyraEditor", "BeginLoadingPIEData", "正在加载PIE数据")); // 创建慢任务
		const bool bShowCancelButton = false; // 不显示取消按钮
		const bool bAllowInPIE = true; // 允许在PIE中
		SlowTask.MakeDialog(bShowCancelButton, bAllowInPIE); // 创建对话框

		const ULyraGameData& LocalGameDataCommon = GetGameData(); // 获取游戏数据

		// 特意在GetGameData之后，避免在此计时器中计算GameData时间
		SCOPE_LOG_TIME_IN_SECONDS(TEXT("PreBeginPIE资源预加载完成"), nullptr); // 记录预加载时间

		// 您可以在此处添加我们将要使用的体验所需的任何其他内容的预加载
		// （例如，通过从世界设置中获取默认体验+开发者设置中的体验覆盖）
	}
}
#endif